package org.pi.common.vision.tool

import org.pi.common.matrix.FloatMatrix

object BorderMode extends Enumeration {
  val Zero = Value
  val Replicate = Value
  val Circular = Value
}

object NormalizeMode extends Enumeration {
  val Normalize = Value
  val NoNormalize = Value
}

trait ConvolutionToolTrait {

	/* applies w times a 1d filter on an image. The filter is applies all s steps/increments. */
	private def filter1d2d(image1: Array[Float],pos0: Int, w: Int, s: Int, filter: Array[Float], image2: Array[Float], bordermode: BorderMode.Value, normalize: NormalizeMode.Value): Unit = {
 		var x: Int = 0;
 		var fx: Int = 0;
		var pos: Int = pos0;
		var pos1: Int = 0;
		var fpos: Int = 0;
		var x1: Int = 0;
		var x2: Int = 0;
		var fsizex: Int = filter.length;
		var fsizex2: Int = Math.floor(fsizex.toFloat/2.0f).toInt;
		
		var norm: Float = 0;
		//if (*image2==0) *image2 = (REAL*)calloc(w*s,sizeof(REAL));
		x=0;
		while (x < fsizex ) {
			if (filter(x)>0) norm += filter(x)
			else norm += (-filter(x));
			x += 1
		}
		if (normalize==NormalizeMode.NoNormalize) norm=1.0f;
		
		x=0;
		while (x<w) { // main loop
			image2(pos) = 0;
			if (x>=fsizex2) x1 = 0
			else x1 = fsizex2-x
			if (w-x>fsizex-fsizex2) {
				x2 = fsizex
			}
			else {
				x2 = (w-x+fsizex2);
			}
			
			if (bordermode==BorderMode.Circular) {
				fpos = 0;
				pos1 = pos0+(w-x1)*s;//(x+x1-fsizex2)*s;
				fx = 0;
				while( fx < x1 ) {
					image2(pos) += filter(fpos) * image1(pos1);
					fpos+=1
					pos1+=s
					fx += 1;
				}
			}
			else if (bordermode==BorderMode.Replicate) {
				fpos = 0;
				pos1 = pos0+(x+x1-fsizex2)*s;
				fx = 0;
				while (fx<x1) {
					image2(pos) += filter(fpos) * image1(pos1);       
					fpos+=1
					fx += 1
				}
			}
			
			fpos = x1;
			pos1 = pos0+(x+x1-fsizex2)*s;
			fx = x1;
			while ( fx < x2 ) {
				image2(pos) += (filter(fpos) * image1(pos1))
				fpos+=1
				pos1+=s
				fx += 1
			}
			
			if (bordermode==BorderMode.Circular) {
				pos1 = pos0;
				fx = x2;
				while ( fx < fsizex  ) {
					image2(pos) += filter(fpos) * image1(pos1);
					fpos+=1
					pos1+=s // error, +1??
					fx += 1
				}
			}
			else if (bordermode==BorderMode.Replicate) {
				pos1 -= s;
				fx = x2;
				while (fx<fsizex) {
					image2(pos) += filter(fpos) * image1(pos1);       
					fpos+=1
					fx +=1
				}
			}
			image2(pos) = image2(pos)/norm;
			
			pos+=s
			x+=1;
		} // end for (main loop)
	} // end def

	def filter2Seperated(image1: FloatMatrix, filter: FloatMatrix, image2: FloatMatrix, d: Int) : FloatMatrix = filter2Seperated(image1, filter, image2, d, BorderMode.Zero, NormalizeMode.NoNormalize) 
	def filter2Seperated(image1: FloatMatrix, filter: FloatMatrix, image2Param: FloatMatrix, d: Int, bordermode: BorderMode.Value, normalize: NormalizeMode.Value) : FloatMatrix = {
		var x: Int = 0
		var y: Int = 0
		var t: Int = 0
		var pos: Int = 0
		var image2 = image2Param
		
		if (image1.shape.size!= 2) throw new Error("filter1d_2d only defined for 2d images")
		
		//if (image2==null) image2=new FloatMatrix( image1.shape : _*)
		if (image2==null) image2=new FloatMatrix( image1.shape :_* )
		val w = image1.length(1)
		val h = image1.length(0)
		//println("w="+w+" h="+h)
		d match {
			case 0 =>
				pos = 0;
				y = 0;
				while (y < w) {
					filter1d2d(image1.data,pos,h,1,filter.data,image2.data, bordermode, normalize);
					pos+=h
					y+=1
				}
			case 1 =>
				pos = 0;
				x = 0;
				while (x < h) {
					filter1d2d(image1.data,pos,w,h,filter.data,image2.data, bordermode, normalize);
					pos+=1
					x+=1
				}
			case _ =>
				throw new Error("error, filter1d_2d: worng dimension!");
		}
		image2
	}

	def filter2d(in:FloatMatrix, kernel: FloatMatrix, outParam: FloatMatrix, normalize: NormalizeMode.Value): FloatMatrix = {
		var out = outParam;
		if (out==null) out = new FloatMatrix(in.shape :_*);
		if (out.hasSameShape(in)) out = new FloatMatrix( in.shape :_* )

		var x: Int=0
		var y: Int=0
		var fx: Int=0
		var fy: Int=0
		var pos:Int=0
		var pos1:Int =0
		var fpos: Int =0
		var x1:Int =0
		var x2:Int=0
		var y1:Int=0
		var y2:Int=0
		val w = in.length(1)
		val h = in.length(0)
		
		val filter = kernel;
		
		var fsizex: Int = kernel.length(1);
		var fsizey: Int = kernel.length(0);
		var fsizex2: Int = Math.floor(fsizex.toDouble/2.0).toInt;
		var fsizey2:Int = Math.floor(fsizey.toDouble/2.0).toInt;
		
		var norm:Float=0.0F;
		var norm1:Float=0.0F;
		var norm2:Float=0.0F;
		
		x=0; while(x<fsizex*fsizey) {norm += filter(x); x+=1}
		x=0; while(x<fsizex*fsizey) {norm1 += Math.max(0.0F,filter(x)); x+=1}
		x=0; while(x<fsizex*fsizey) {norm2 += Math.max(0.0F,-filter(x)); x+=1}
		//if (norm==0) norm = (float)Math.max(norm1,norm2);
		norm = Math.max(norm1,norm2);
		if (normalize==NormalizeMode.NoNormalize || norm==0) norm=1;
		//System.out.println("norm="+norm);

		pos=0;
		x=0;
		while(x<w) {
			y=0;
			while(y<h){
				var localResult:Float=0.0F;
				x1 = if(x>=fsizex2) 0 else (fsizex2-x);
				x2 = if(w-x>fsizex-fsizex2) fsizex else (w-x+fsizex2);
				y1 = if(y>=fsizey2) 0 else (fsizey2-y);
				y2 = if(h-y>fsizey-fsizey2) fsizey else (h-y+fsizey2);
				fx=x1
				while(fx<x2) {
					fy=y1;
					fpos = fy+fx*fsizey;
					pos1 = (y+fy-fsizey2)+(x+fx-fsizex2)*h;
					while(fy<y2) {
						localResult += filter(fpos) * in(pos1);
						fy+=1
						fpos+=1
						pos1+=1
					}
					fx+=1
				}
				out(pos) = localResult/norm;
				y+=1
				pos+=1
			}
			x+=1;
		}

		out;
	}
}

object ConvolutionTool extends ConvolutionToolTrait {

	def main(args: Array[String]) : Unit =  {
		println("main conv start")
		import org.pi.common.vision.Api.imread
		import org.pi.common.vision.Api.imwrite
		import org.pi.common.vision.Api.rgb2gray
		import org.pi.common.time.Api._
		var C = imread("data/4Spiele.png")
		var G = rgb2gray(C)
		var F = FloatMatrix( (-1.0f) :: 0.0f :: 1.0f :: Nil )
		var L = FloatMatrix( 0.0f :: 1.0f :: 0.0f :: 1.0f :: (-4.0f) :: 1.0f :: 0.0f :: 1.0f :: 0.0f :: Nil )
		L = L.reshape(3,3)
		var FGy = filter2Seperated(G,F,null,0,BorderMode.Zero,NormalizeMode.NoNormalize)
		var FGx = filter2Seperated(G,F,null,1,BorderMode.Zero,NormalizeMode.NoNormalize)
		tic
		var LG = filter2d(G,L,null,NormalizeMode.NoNormalize)
		toc("filter2d with laplace")
		imwrite("gray.png",G)
		imwrite("gray_filtered_y.png",FGy)
		imwrite("gray_filtered_x.png",FGx)
		imwrite("gray_filtered_laplace.png",LG)
		
		println("main conv end")
	}
}
